/*******************************************************************************
* This file provides the functions for the ADC module.
*
* Author: Kong Wai Weng @ Cytron Technologies Sdn. Bhd.
*******************************************************************************/



#include "hal_adc.h"



/*******************************************************************************
* PRIVATE CONSTANT DEFINITION                                                  *
*******************************************************************************/

// ADC channel for the Vin voltage.
#define VIN_ADC_CHANNEL	0

// ADC channel for the current sensor.
#define CURRENT_ADC_CHANNEL	11

// Number of AN channel to scan.
#define NUMBER_OF_CHANNEL		3

// Buffer size for each channel.
#define BUFFER_SIZE				100



/*******************************************************************************
* PRIVATE GLOBAL VARIABLES                                                     *
*******************************************************************************/

// Buffer for DMA RAM.
// Size of the array must be aligned with the number of AN channel to scan.
static unsigned int prv_uiDmaBufferA[BUFFER_SIZE * NUMBER_OF_CHANNEL] __attribute__((space(dma)));
static unsigned int prv_uiDmaBufferB[BUFFER_SIZE * NUMBER_OF_CHANNEL] __attribute__((space(dma)));

// Flag to indicate the data in the buffer is ready.
static unsigned char ucAdcDataReady = 0;

// ADC offset for the current sensor.
static unsigned int uiCurrentAdcOffset = 0;



/*******************************************************************************
* PUBLIC FUNCTION: vInitAdc
*
* PARAMETERS:
* ~ void
*
* RETURN:
* ~ void
*
* DESCRIPTIONS:
* Initialize the ADC module.
*
*******************************************************************************/
void vInitAdc(void)
{
    // Disable the DMA module if enabled previously.
    CloseDMA0();

    // Configure DMA interrupt.
    ConfigIntDMA0(  DMA0_INT_ENABLE &   // DMA interrupt enabled.
                    DMA0_INT_PRI_4 );   // DMA interrupt priority 4.

    // Open DMA module.
    OpenDMA0(	DMA0_MODULE_ON &			// Turn on DMA module.
                DMA0_SIZE_WORD &			// Data transfer size = word.
                PERIPHERAL_TO_DMA0 &			// Read from peripheral address; write to DMA RAM address.
                DMA0_INTERRUPT_BLOCK &			// Initiate interrupt when all the data has been moved.
                DMA0_NORMAL &				// Null Data Peripheral Write Mode = Normal.
                DMA0_REGISTER_POST_INCREMENT &		// Register Indirect with Post-Increment mode.
                DMA0_CONTINUOUS,			// DMA Channel Operating Mode = Continuous, Ping-Pong modes disabled.

                0b1101,					// IRQ = ADC 1.

                __builtin_dmaoffset(prv_uiDmaBufferA),	// Primary DMA RAM Start Address.

                __builtin_dmaoffset(prv_uiDmaBufferB),	// Secondary DMA RAM Start Address.

                (unsigned int)&ADC1BUF0,		// Peripheral address.

                sizeof(prv_uiDmaBufferA) / 2 - 1 );	// Number of words to be transfered.



    // Disable the ADC module if enabled previously.
    CloseADC1();
    CloseADC2();

    // Configure ADC interrupt.
    ConfigIntADC1(  ADC_INT_DISABLE &	// ADC interrupt disabled.
                    ADC_INT_PRI_0 );	// ADC interrupt priority 0.

    // Select AN0.
    SetChanADC1(    ADC_CH123_NEG_SAMPLEA_VREFN &	// Sample A, CH123 negative input = Vrefn.
                    ADC_CH123_NEG_SAMPLEB_VREFN &	// Sample B, CH123 negative input = Vrefn.
                    ADC_CH123_POS_SAMPLEA_0_1_2 &	// Sample A, CH123 positive input = AN0-2.
                    ADC_CH123_POS_SAMPLEB_0_1_2,	// Sample B, CH123 positive input = AN0-2.

                    ADC_CH0_POS_SAMPLEA_AN0 &		// Sample A, CH0 positive input = AN0.
                    ADC_CH0_NEG_SAMPLEA_VREFN &		// Sample A, CH0 negative input = Vrefn.
                    ADC_CH0_POS_SAMPLEB_AN0 &		// Sample B, CH0 positive input = AN0.
                    ADC_CH0_NEG_SAMPLEB_VREFN );	// Sample B, CH0 negative input = Vrefn.


    // There is a bug in the library where the following registers are not set.
    // Thus we need to configure them manually.
    AD1PCFGH = ENABLE_ALL_DIG_16_31;
    AD1CSSH = SCAN_NONE_16_31;

//	// We don't use ADC module 2, but we need to configure the analog and digital pin as well.
//	AD2PCFGL = ENABLE_AN0_ANA &	ENABLE_AN1_ANA;

    // Open ADC module 1.
    OpenADC1(	ADC_MODULE_ON &		// Turn on ADC module.
                ADC_IDLE_CONTINUE &	// Continue module operation in Idle mode.
                ADC_ADDMABM_SCATTR &	// DMA buffers are written in the scatter gather mode (Don't care).
                ADC_AD12B_12BIT &	// 12-bit, 1-channel ADC operation.
                ADC_FORMAT_INTG &	// Data output format = integer.
                ADC_CLK_AUTO &		// Internal counter ends sampling and starts conversion (auto-convert).
                ADC_AUTO_SAMPLING_ON &	// Sampling begins immediately after last conversion. SAMP bit is auto-set.
                ADC_MULTIPLE &		// Samples multiple channels individually in sequence.
                ADC_SAMP_OFF,		// Stop sampling.

                ADC_VREF_AVDD_AVSS &	// Voltage reference = AVDD & AVSS.
                ADC_SCAN_ON &		// Scan inputs for CH0+ during Sample A bit.
                ADC_SELECT_CHAN_0 &	// Only convert CH0.
                ADC_DMA_ADD_INC_3 &	// Generates interrupt after every 3 sample/conversion operation.
                ADC_ALT_BUF_OFF &	// Always starts filling buffer from the beginning.
                ADC_ALT_INPUT_OFF,	// Always uses channel input selects for Sample A.

                ADC_SAMPLE_TIME_26 &	// Auto sample time = 26 TAD.
                ADC_CONV_CLK_SYSTEM &	// Clock derived from system clock.
                ADC_CONV_CLK_100Tcy,	// TAD = 100 TCY (2.5us). Conversion time = 14 TAD (12 bits).

                ADC_DMA_BUF_LOC_1,	// Allocates 1 word of buffer to each analog input (Don't care).

                ENABLE_AN0_ANA &	// AN0, AN1 and AN11 as analog input, AN2-10, 12-15 are digital input.
                ENABLE_AN1_ANA &
                ENABLE_AN11_ANA,

                ENABLE_ALL_DIG_16_31,	// AN16-30 are digital input.

                SCAN_NONE_16_31,	// Do not scan AN16-30.

                SCAN_ALL_0_15 &		// Scan AN0, AN1 and AN11.
                SKIP_SCAN_AN2 &
                SKIP_SCAN_AN3 &
                SKIP_SCAN_AN4 &
                SKIP_SCAN_AN5 &
                SKIP_SCAN_AN6 &
                SKIP_SCAN_AN7 &
                SKIP_SCAN_AN8 &
                SKIP_SCAN_AN9 &
                SKIP_SCAN_AN10 &
                SKIP_SCAN_AN12 &
                SKIP_SCAN_AN13 &
                SKIP_SCAN_AN14 &
                SKIP_SCAN_AN15	);


}	



/*******************************************************************************
* PUBLIC FUNCTION: uiReadAdc
*
* PARAMETERS:
* ~ ucChannel	- The ADC channel that we want to read.
*
* RETURN:
* ~ The ADC result.
*
* DESCRIPTIONS:
* Read the ADC result
*
*******************************************************************************/
unsigned int uiReadAdc(unsigned char ucChannel)
{
    unsigned long ulSum = 0;
    unsigned int uiResult;
    unsigned int i;

    // Only calculate the average value when the ADC data is ready.
    if (ucAdcDataReady == 1) {
        switch (ucChannel) {
            case 0:
                for (i = 0; i < (sizeof(prv_uiDmaBufferA) / 2); i += NUMBER_OF_CHANNEL) {
                        ulSum += prv_uiDmaBufferA[i];
                }
                uiResult = (unsigned int)(ulSum / BUFFER_SIZE);
                break;

            case 1:
                for (i = 1; i < (sizeof(prv_uiDmaBufferA) / 2); i += NUMBER_OF_CHANNEL) {
                        ulSum += prv_uiDmaBufferA[i];
                }
                uiResult = (unsigned int)(ulSum / BUFFER_SIZE);
                break;

            case 11:
                for (i = 2; i < (sizeof(prv_uiDmaBufferA) / 2); i += NUMBER_OF_CHANNEL) {
                        ulSum += prv_uiDmaBufferA[i];
                }
                uiResult = (unsigned int)(ulSum / BUFFER_SIZE);
                break;

            default:
                uiResult = 0;
                break;
        }
    }
    else {
            uiResult = 0;
    }

    return uiResult;
}	



/*******************************************************************************
* PUBLIC FUNCTION: uiReadVin
*
* PARAMETERS:
* ~ void
*
* RETURN:
* ~ The voltage of Vin in mV.
*
* DESCRIPTIONS:
* Read the voltage of Vin.
*
*******************************************************************************/
unsigned int uiReadVin(void)
{
    // Read the ADC value.
    unsigned int uiVinAdc = uiReadAdc(VIN_ADC_CHANNEL);

    // Calculate the Vin in mV and return the value.
    unsigned int uiVin = (unsigned int)((float)uiVinAdc * (3.3f / 4095.0f * 6.2f / 1.5f * 1000.0f));

    return uiVin;
}



/*******************************************************************************
 * PUBLIC FUNCTION: vCalibrateCurrentOffset
 *
 * PARAMETERS:
 * ~ void
 *
 * RETURN:
 * ~ void
 *
 * DESCRIPTIONS:
 * Calibrate the current sensor offset.
 *
 *******************************************************************************/
void vCalibrateCurrentOffset(void)
{
    uiCurrentAdcOffset = uiReadAdc(CURRENT_ADC_CHANNEL);
}



/*******************************************************************************
 * PUBLIC FUNCTION: uiReadCurrent
 *
 * PARAMETERS:
 * ~ void
 *
 * RETURN:
 * ~ The current in mA.
 *
 * DESCRIPTIONS:
 * Read the current.
 *
 *******************************************************************************/
unsigned int uiReadCurrent(void)
{
    unsigned int uiCurrent;

    // Read the ADC value.
    unsigned int uiCurrentAdc = uiReadAdc(CURRENT_ADC_CHANNEL);

    if (uiCurrentAdc > uiCurrentAdcOffset) {
        // Calculate the current in mA and return the value.
        // Sensitivity = 3.231/5V * 40mV/A.
        uiCurrent = (unsigned int)((float)(uiCurrentAdc - uiCurrentAdcOffset) * 3.3f / 4095.0f / 0.025848f * 1000.0f);
    }
    else {
        uiCurrent = 0;
    }
    
    return uiCurrent;
}



/*******************************************************************************
* ISR: Interrupt for DMA 0 (ADC 1)
*
* DESCRIPTIONS:
* Calculate the average value of ADC 1.
*
*******************************************************************************/
void __attribute__((interrupt, no_auto_psv)) _DMA0Interrupt(void)
{
	// Clear the interrupt flag.
	IFS0bits.DMA0IF = 0;
	
	// Indicate the data is ready.
	ucAdcDataReady = 1;
}	
